﻿using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Net.Http.Json;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Internal;

namespace Crgd.UAuthServer.Sdk
{
    /// <summary>
    /// 互访应用HTTP客户端
    /// </summary>
    public abstract class AppClient
    {
        public AppClient(AppHttpClientOptions options, AppAuthServer authServer) : base()
        {
            this.Options = options;
            this.authServer = authServer;
            this.httpClient = new HttpClient();
            this.httpClient.BaseAddress = new Uri(this.Options.AppBaseUrl);
        }

        public AppHttpClientOptions Options { get; }

        private readonly AppAuthServer authServer;


        private readonly HttpClient httpClient;

        public bool IsAuthenticationed
        {
            get; private set;
        }

        /// <summary>
        /// 存储服务端返回的TOKEN
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        protected abstract Task SaveResponseTokenAsync(HttpResponseMessage response);

        protected abstract Task ClearTokenAsync();

        /// <summary>
        /// 赋予请求TOKEN
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected abstract Task GiveRequestTokenAsync(HttpRequestMessage request);

        private readonly AsyncLock _mutex = new AsyncLock();

        protected async Task<T> InternalRequestAsync<T>(HttpMethod method, string url, object body = null)
        {
            var response = await this.RequestAsync(method, url, body == null ? JsonContent.Create(body) : null);

            var result = await response.Content.ReadFromJsonAsync<XnRestfulResult<T>>();
            if (!result.Success)
            {
                var error = new Exception(result.Message.ToString());
                error.Data.Add("Response", result);
                throw error;
            }
            return result.Data;
        }

        public async Task<HttpResponseMessage> RequestAsync(HttpMethod method, string url, HttpContent body = null, IDictionary<string, string> headers = null)
        {
            // 异步锁避免并发导致多次登录
            using (await _mutex.LockAsync())
            {
                // 如果未登录，先登录
                if (!this.IsAuthenticationed)
                {
                    await this.AuthenticationAsync();
                }
            }
            var request = new HttpRequestMessage(method, url);
            if (this.IsAuthenticationed)
            {
                await this.GiveRequestTokenAsync(request);
            }
            request.Content = body;
            if (headers != null)
            {
                foreach (var key in headers.Keys)
                {
                    request .Headers.Add(key, headers[key]);
                }
            }
            var response = await this.httpClient.SendAsync(request);

            // 注意：此处无论是否已登录，都要做此动作，是为了无缝刷新ACCESS_TOKEN有意为之。
            await this.SaveResponseTokenAsync(response);
            return response;
        }

        public Task<HttpResponseMessage> GetAsync(string url, IDictionary<string, string> headers = null)
        {
            return RequestAsync(HttpMethod.Get, url, headers: headers);
        }

        public Task<HttpResponseMessage> PostAsync(string url, HttpContent body = null, IDictionary<string, string> headers = null)
        {
            return RequestAsync(HttpMethod.Post, url, body: body, headers: headers);
        }

        public Task<HttpResponseMessage> PutAsync(string url, HttpContent body = null, IDictionary<string, string> headers = null)
        {
            return RequestAsync(HttpMethod.Put, url, body: body, headers: headers);
        }

        public Task<HttpResponseMessage> DeleteAsync(string url, IDictionary<string, string> headers = null)
        {
            return RequestAsync(HttpMethod.Delete, url, headers: headers);
        }

        protected async Task AuthenticationAsync()
        {
            var sessionKey = await this.authServer.GetSessionKeyAsync(this.Options.AppId, this.Options.TenantId );
            var url = new UriBuilder(this.Options.AppBaseUrl)
                .WithPathSegment(this.Options.AppLoginPath)
                .WithParameter("sessionKey", sessionKey)
                .WithParameter("tenantId", this.Options.TenantId);

            await this.InternalRequestAsync<object>(HttpMethod.Get, url.ToString());
            this.IsAuthenticationed = true;
        }

        /// <summary>
        /// 从AuthServer登出
        /// </summary>
        /// <returns></returns>
        protected async Task DeuthenticationAsync()
        {
            using (await _mutex.LockAsync())
            {
                await this.ClearTokenAsync();
                this.IsAuthenticationed = false;
            }
        }
    }
}
